// Go作者版权所有。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

package tar

import "strings"

// 格式表示tar存档格式。
// None
// 最初的tar格式是在unixv7中引入的。
// 从那时起，有多种竞争形式试图
// 标准化或扩展V7格式以克服其局限性。
// 最常见的格式是USTAR、PAX和GNU格式，
// 每个人都有自己的优势和局限性。
// None
// 下表显示了每种格式的功能：
// None
// |乌斯塔|帕克斯| GNU
// ------------------+--------+-----------+----------
// 名称| 256B |无限|无限
// Linkname | 100B |无限|无限
// 尺寸| uint33 |无限制| uint89
// 模式| uint21 | uint21 | uint57
// Uid/Gid | uint21 |无限| uint57
// Uname/Gname | 32B |无限| 32B
// ModTime | uint33 |无限| int89
// 访问时间|不适用|无限制| int89
// 更改时间|不适用|无限| int89
// Devmajor/Devminor | uint21 | uint21 | uint57
// ------------------+--------+-----------+----------
// 字符串编码| ASCII | UTF-8 |二进制
// 次秒|否|是|否
// 稀疏文件|否|是|是
// None
// 表的上半部分显示了标题字段，其中每种格式都有报告
// 每个字符串字段允许的最大字节数，以及
// 用于存储每个数字字段的整数类型
// （其中时间戳存储为自Unix纪元以来的秒数）。
// None
// 表格的下半部分显示了每种格式的特殊功能，
// 例如支持的字符串编码，支持次秒时间戳，
// 或支持稀疏文件。
// None
// 编写器当前不支持稀疏文件。
type Format int

// 用于标识各种tar格式的常量。
const (
	// 故意对公共API隐藏常量的含义。
	_ Format = (1 << iota) / 4 // 0、0、1、2、4、8等的顺序。。。

	// FormatUnknown表示格式未知。
	FormatUnknown

	// 标准化之前原始Unix V7 tar工具的格式。
	formatV7

	// FormatUSTAR表示POSIX.1-1988中定义的USTAR头格式。
	// None
	// 虽然这种格式与大多数tar阅读器兼容，
	// 该格式有几个限制，不适合某些用途。
	// 最值得注意的是，它不能支持稀疏文件，即大于8GiB的文件，
	// 大于256个字符的文件名和非ASCII文件名。
	// None
	// 参考：
	// http:
	FormatUSTAR

	// FormatPAX表示POSIX.1-2001中定义的PAX标头格式。
	// None
	// PAX通过使用Typeflag TypeXHeader编写一个特殊文件来扩展USTAR
	// 在原始标题之前。此文件包含一组键值
	// 用于克服USTAR缺点的记录，以及
	// 提供时间戳的亚秒分辨率。
	// None
	// 一些较新的格式通过定义它们的
	// 拥有键并为相关值指定特定语义。
	// 例如，PAX中的稀疏文件支持是使用密钥实现的
	// 由GNU手册定义（例如，“GNU.sparse.map”）。
	// None
	// 参考：
	// http:
	FormatPAX

	// FormatGNU表示GNU头格式。
	// None
	// GNU标头格式比USTAR和PAX标准和
	// 与它们不兼容。GNU格式支持
	// 任意文件大小、任意编码和长度的文件名，
	// 稀疏文件和其他功能。
	// None
	// 建议选择PAX而不是GNU，除非目标
	// 应用程序只能解析GNU格式的归档文件。
	// None
	// 参考：
	// https:
	FormatGNU

	// Schily的tar格式，与USTAR不兼容。
	// 这不包括PAX格式的星形扩展；这些属于
	// PAX格式。
	formatSTAR

	formatMax
)

func (f Format) has(f2 Format) bool   { return f&f2 != 0 }
func (f *Format) mayBe(f2 Format)     { *f |= f2 }
func (f *Format) mayOnlyBe(f2 Format) { *f &= f2 }
func (f *Format) mustNotBe(f2 Format) { *f &^= f2 }

var formatNames = map[Format]string{
	formatV7: "V7", FormatUSTAR: "USTAR", FormatPAX: "PAX", FormatGNU: "GNU", formatSTAR: "STAR",
}

func (f Format) String() string {
	var ss []string
	for f2 := Format(1); f2 < formatMax; f2 <<= 1 {
		if f.has(f2) {
			ss = append(ss, formatNames[f2])
		}
	}
	switch len(ss) {
	case 0:
		return "<unknown>"
	case 1:
		return ss[0]
	default:
		return "(" + strings.Join(ss, " | ") + ")"
	}
}

// 用于识别各种格式的魔法。
const (
	magicGNU, versionGNU     = "ustar ", " \x00"
	magicUSTAR, versionUSTAR = "ustar\x00", "00"
	trailerSTAR              = "tar\x00"
)

// 各种焦油规格的尺寸常数。
const (
	blockSize  = 512 // 焦油流中每个块的大小
	nameSize   = 100 // USTAR格式的名称字段的最大长度
	prefixSize = 155 // USTAR格式的前缀字段的最大长度
)

// 块填充计算将偏移量填充到
// 最近的块边，其中0<=n<块大小。
func blockPadding(offset int64) (n int64) {
	return -offset & (blockSize - 1)
}

var zeroBlock block

type block [blockSize]byte

// 将块转换为任意数量的格式。
func (b *block) V7() *headerV7       { return (*headerV7)(b) }
func (b *block) GNU() *headerGNU     { return (*headerGNU)(b) }
func (b *block) STAR() *headerSTAR   { return (*headerSTAR)(b) }
func (b *block) USTAR() *headerUSTAR { return (*headerUSTAR)(b) }
func (b *block) Sparse() sparseArray { return sparseArray(b[:]) }

// GetFormat基于校验和检查块是否为有效的tar头。
// 然后，它尝试根据魔法值猜测特定的格式。
// 如果校验和失败，则返回FormatUnknown。
func (b *block) GetFormat() Format {
	// 验证校验和。
	var p parser
	value := p.parseOctal(b.V7().Chksum())
	chksum1, chksum2 := b.ComputeChecksum()
	if p.err != nil || (value != chksum1 && value != chksum2) {
		return FormatUnknown
	}

	// 猜猜神奇的值。
	magic := string(b.USTAR().Magic())
	version := string(b.USTAR().Version())
	trailer := string(b.STAR().Trailer())
	switch {
	case magic == magicUSTAR && trailer == trailerSTAR:
		return formatSTAR
	case magic == magicUSTAR:
		return FormatUSTAR | FormatPAX
	case magic == magicGNU && version == versionGNU:
		return FormatGNU
	default:
		return formatV7
	}
}

// SetFormat写入指定格式所需的魔法值
// 然后相应地更新校验和。
func (b *block) SetFormat(format Format) {
	// 设置魔法值。
	switch {
	case format.has(formatV7):
		// 什么也不做。
	case format.has(FormatGNU):
		copy(b.GNU().Magic(), magicGNU)
		copy(b.GNU().Version(), versionGNU)
	case format.has(formatSTAR):
		copy(b.STAR().Magic(), magicUSTAR)
		copy(b.STAR().Version(), versionUSTAR)
		copy(b.STAR().Trailer(), trailerSTAR)
	case format.has(FormatUSTAR | FormatPAX):
		copy(b.USTAR().Magic(), magicUSTAR)
		copy(b.USTAR().Version(), versionUSTAR)
	default:
		panic("invalid format")
	}

	// 更新校验和。
	// 此字段的特殊之处在于，它以NULL then空格结尾。
	var f formatter
	field := b.V7().Chksum()
	chksum, _ := b.ComputeChecksum() // 可能的值为256..128776
	f.formatOctal(field[:7], chksum) // 自128776<262143以来从未失败
	field[7] = ' '
}

// ComputeChecksum计算头块的校验和。
// POSIX指定了无符号字节值的总和，但使用了Sun tar
// 有符号字节值。
// 我们计算并返回两者。
func (b *block) ComputeChecksum() (unsigned, signed int64) {
	for i, c := range b {
		if 148 <= i && i < 156 {
			c = ' ' // 将校验和字段本身视为所有空格。
		}
		unsigned += int64(c)
		signed += int64(int8(c))
	}
	return unsigned, signed
}

// 重置清除所有零的块。
func (b *block) Reset() {
	*b = block{}
}

type headerV7 [blockSize]byte

func (h *headerV7) Name() []byte     { return h[000:][:100] }
func (h *headerV7) Mode() []byte     { return h[100:][:8] }
func (h *headerV7) UID() []byte      { return h[108:][:8] }
func (h *headerV7) GID() []byte      { return h[116:][:8] }
func (h *headerV7) Size() []byte     { return h[124:][:12] }
func (h *headerV7) ModTime() []byte  { return h[136:][:12] }
func (h *headerV7) Chksum() []byte   { return h[148:][:8] }
func (h *headerV7) TypeFlag() []byte { return h[156:][:1] }
func (h *headerV7) LinkName() []byte { return h[157:][:100] }

type headerGNU [blockSize]byte

func (h *headerGNU) V7() *headerV7       { return (*headerV7)(h) }
func (h *headerGNU) Magic() []byte       { return h[257:][:6] }
func (h *headerGNU) Version() []byte     { return h[263:][:2] }
func (h *headerGNU) UserName() []byte    { return h[265:][:32] }
func (h *headerGNU) GroupName() []byte   { return h[297:][:32] }
func (h *headerGNU) DevMajor() []byte    { return h[329:][:8] }
func (h *headerGNU) DevMinor() []byte    { return h[337:][:8] }
func (h *headerGNU) AccessTime() []byte  { return h[345:][:12] }
func (h *headerGNU) ChangeTime() []byte  { return h[357:][:12] }
func (h *headerGNU) Sparse() sparseArray { return sparseArray(h[386:][:24*4+1]) }
func (h *headerGNU) RealSize() []byte    { return h[483:][:12] }

type headerSTAR [blockSize]byte

func (h *headerSTAR) V7() *headerV7      { return (*headerV7)(h) }
func (h *headerSTAR) Magic() []byte      { return h[257:][:6] }
func (h *headerSTAR) Version() []byte    { return h[263:][:2] }
func (h *headerSTAR) UserName() []byte   { return h[265:][:32] }
func (h *headerSTAR) GroupName() []byte  { return h[297:][:32] }
func (h *headerSTAR) DevMajor() []byte   { return h[329:][:8] }
func (h *headerSTAR) DevMinor() []byte   { return h[337:][:8] }
func (h *headerSTAR) Prefix() []byte     { return h[345:][:131] }
func (h *headerSTAR) AccessTime() []byte { return h[476:][:12] }
func (h *headerSTAR) ChangeTime() []byte { return h[488:][:12] }
func (h *headerSTAR) Trailer() []byte    { return h[508:][:4] }

type headerUSTAR [blockSize]byte

func (h *headerUSTAR) V7() *headerV7     { return (*headerV7)(h) }
func (h *headerUSTAR) Magic() []byte     { return h[257:][:6] }
func (h *headerUSTAR) Version() []byte   { return h[263:][:2] }
func (h *headerUSTAR) UserName() []byte  { return h[265:][:32] }
func (h *headerUSTAR) GroupName() []byte { return h[297:][:32] }
func (h *headerUSTAR) DevMajor() []byte  { return h[329:][:8] }
func (h *headerUSTAR) DevMinor() []byte  { return h[337:][:8] }
func (h *headerUSTAR) Prefix() []byte    { return h[345:][:155] }

type sparseArray []byte

func (s sparseArray) Entry(i int) sparseElem { return sparseElem(s[i*24:]) }
func (s sparseArray) IsExtended() []byte     { return s[24*s.MaxEntries():][:1] }
func (s sparseArray) MaxEntries() int        { return len(s) / 24 }

type sparseElem []byte

func (s sparseElem) Offset() []byte { return s[00:][:12] }
func (s sparseElem) Length() []byte { return s[12:][:12] }
